/*
 *  Copyright 2019-2020 CY
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package me.zhengjie.modules.security.rest;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.wf.captcha.base.Captcha;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.annotation.Log;
import me.zhengjie.annotation.rest.AnonymousDeleteMapping;
import me.zhengjie.annotation.rest.AnonymousGetMapping;
import me.zhengjie.annotation.rest.AnonymousPostMapping;
import me.zhengjie.config.RsaProperties;
import me.zhengjie.exception.BadRequestException;
import me.zhengjie.modules.security.config.bean.LoginCodeEnum;
import me.zhengjie.modules.security.config.bean.LoginProperties;
import me.zhengjie.modules.security.config.bean.SecurityProperties;
import me.zhengjie.modules.security.domain.WeiXinDto;
import me.zhengjie.modules.security.domain.WeiXinVo;
import me.zhengjie.modules.security.security.TokenProvider;
import me.zhengjie.modules.security.service.dto.AuthUserDto;
import me.zhengjie.modules.security.service.dto.JwtUserDto;
import me.zhengjie.modules.security.service.OnlineUserService;
import me.zhengjie.modules.system.domain.Job;
import me.zhengjie.modules.system.domain.Role;
import me.zhengjie.modules.system.domain.User;
import me.zhengjie.modules.system.service.UserService;
import me.zhengjie.utils.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author CY
 * @date 2018-11-23
 * 授权、根据token获取用户详细信息
 */
@Slf4j
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@Api(tags = "系统：系统授权接口")
public class AuthorizationController {
    private final SecurityProperties properties;
    private final RedisUtils redisUtils;
    private final OnlineUserService onlineUserService;
    private final TokenProvider tokenProvider;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;
    @Resource
    private LoginProperties loginProperties;
    @Resource
    private final UserService userService;
    @Resource
    private VxMsg vxMsg;
    private final PasswordEncoder passwordEncoder;

    @Log("用户登录")
    @ApiOperation("登录授权")
    @AnonymousPostMapping(value = "/login")
    public ResponseEntity<Object> login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
        // 密码解密
        String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
        // 查询验证码
        String code = (String) redisUtils.get(authUser.getUuid());
        // 清除验证码
        redisUtils.del(authUser.getUuid());
        if (StringUtils.isBlank(code)) {
            throw new BadRequestException("验证码不存在或已过期");
        }
        if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
            throw new BadRequestException("验证码错误");
        }
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 生成令牌与第三方系统获取令牌方式
        // UserDetails userDetails = userDetailsService.loadUserByUsername(userInfo.getUsername());
        // Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        // SecurityContextHolder.getContext().setAuthentication(authentication);
        String token = tokenProvider.createToken(authentication);
        final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
        // 返回 token 与 用户信息
        Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
            put("token", properties.getTokenStartWith() + token);
            put("user", jwtUserDto);
        }};
        if (loginProperties.isSingleLogin()) {
            // 踢掉之前已经登录的token
            onlineUserService.kickOutForUsername(authUser.getUsername());
        }
        // 保存在线信息
        onlineUserService.save(jwtUserDto, token, request);
        // 返回登录信息
        return ResponseEntity.ok(authInfo);
    }

    @ApiOperation("获取用户信息")
    @GetMapping(value = "/info")
    public ResponseEntity<UserDetails> getUserInfo() {
        return ResponseEntity.ok(SecurityUtils.getCurrentUser());
    }

    @ApiOperation("获取验证码")
    @AnonymousGetMapping(value = "/code")
    public ResponseEntity<Object> getCode() {
        // 获取运算的结果
        Captcha captcha = loginProperties.getCaptcha();
        String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
        //当验证码类型为 arithmetic时且长度 >= 2 时，captcha.text()的结果有几率为浮点型
        String captchaValue = captcha.text();
        if (captcha.getCharType() - 1 == LoginCodeEnum.ARITHMETIC.ordinal() && captchaValue.contains(".")) {
            captchaValue = captchaValue.split("\\.")[0];
        }
        // 保存
        redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
        // 验证码信息
        Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
            put("img", captcha.toBase64());
            put("uuid", uuid);
        }};
        return ResponseEntity.ok(imgResult);
    }

    @ApiOperation("退出登录")
    @AnonymousDeleteMapping(value = "/logout")
    public ResponseEntity<Object> logout(HttpServletRequest request) {
        String token = tokenProvider.getToken(request);
        onlineUserService.logout(token);
        return new ResponseEntity<>(HttpStatus.OK);
    }


    @Log("开发用的登录")
    @ApiOperation("开发用的登录授权")
    @AnonymousPostMapping(value = "/loginKaiFar")
    public ResponseEntity<Object> loginKaiFar(@RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
        // 密码解密
        authUser.setUsername("admin");
        authUser.setPassword("t4V/WLf4OgB9fFZ+X12paPt5CTr3hdfVW5C5601N                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 eGBt/IFZ4soUau2FeOMdIvhf8FJiU7RlgZeRm/URPQXAaw==");
        String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 生成令牌与第三方系统获取令牌方式
        // UserDetails userDetails = userDetailsService.loadUserByUsername(userInfo.getUsername());
        // Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        // SecurityContextHolder.getContext().setAuthentication(authentication);
        String token = tokenProvider.createToken(authentication);
        final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
        // 返回 token 与 用户信息
        Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
            put("token", properties.getTokenStartWith() + token);
            put("user", jwtUserDto);
        }};
        if (loginProperties.isSingleLogin()) {
            // 踢掉之前已经登录的token
            onlineUserService.kickOutForUsername(authUser.getUsername());
        }
        // 保存在线信息
        onlineUserService.save(jwtUserDto, token, request);
        // 返回登录信息
        return ResponseEntity.ok(authInfo);
    }

    @Log("微信小程序登录")
    @ApiOperation("微信小程序登录")
    @AnonymousPostMapping(value = "/loginWei")
    public ResponseEntity<Object> loginWei(@RequestBody WeiXinVo authUser, HttpServletRequest request) throws Exception {
        String jsonStr = vxMsg.vxLogin(authUser.getCode());
        JSONObject json = JSONUtil.parseObj(jsonStr);
        log.info(json.toString());
        if(json.containsKey("errcode")){
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        User user = userService.findByName(json.getStr("openid"));
        WeiXinDto weiXinDto = new WeiXinDto(json.getStr("openid"),json.getStr("session_key"),user==null?"0":"1");
        return ResponseEntity.ok(weiXinDto);
    }

    @Log("微信小程序登录openId")
    @ApiOperation("微信小程序登录openId")
    @AnonymousPostMapping(value = "/loginByOpenId")
    public ResponseEntity<Object> loginByOpenId(@RequestBody WeiXinVo authUser, HttpServletRequest request) throws Exception {
        Map<String, Object> authInfo = new HashMap<String, Object>();
        if("0".equals(authUser.getStatus())){
            String jsonStr = vxMsg.vxPhoneUrl(authUser.getCode());
            log.info(jsonStr);
            JSONObject json = JSONUtil.parseObj(jsonStr);
            if(json.getInt("errcode")!=0){
                return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
            }
            String phoneInfo = json.get("phone_info").toString();
            json = JSONUtil.parseObj(phoneInfo);
            String phone = json.getStr("purePhoneNumber");
            User user = userService.findByPhone(phone);
            if (user == null) {
                authInfo.put("code", "304");
                return ResponseEntity.ok(authInfo);
            }
            user.setUsername(authUser.getOpenid());
            userService.updateById(user);

        }

        User user = userService.findByName(authUser.getOpenid());
        if("1".equals(user.getStatus())){
            log.info("微信小程序登录openId 未申请");
            authInfo.put("code", "302");
            return ResponseEntity.ok(authInfo);
        } else if ("2".equals(user.getStatus())) {
            log.info("微信小程序登录openId 未审批");
            authInfo.put("code", "303");
            return ResponseEntity.ok(authInfo);
        }
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(authUser.getOpenid(), "123456");
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String token = tokenProvider.createToken(authentication);
        final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
        jwtUserDto.getUser().setPassword( null);
        // 返回 token 与 用户信息
        log.info("微信小程序登录openId 登录成功");
        authInfo.put("token", properties.getTokenStartWith() + token);
        authInfo.put("user", jwtUserDto);
        authInfo.put("code", "200");
        if (loginProperties.isSingleLogin()) {
            // 踢掉之前已经登录的token
            onlineUserService.kickOutForUsername(authUser.getOpenid());
        }
        // 保存在线信息
        onlineUserService.save(jwtUserDto, token, request);
        // 返回登录信息
        return ResponseEntity.ok(authInfo);
    }



    @Log("新微信小程序登录")
    @ApiOperation("新微信小程序登录")
    @AnonymousPostMapping(value = "/weChatLogin")
    public ResponseEntity<Object> weChatLogin(@RequestBody WeiXinVo authUser, HttpServletRequest request) throws Exception {
        Map<String, Object> authInfo = new HashMap<String, Object>(3);
        String jsonStr = vxMsg.newVxLogin(authUser.getCode());
        JSONObject json = JSONUtil.parseObj(jsonStr);
        log.info(json.toString());
        if(json.containsKey("errcode")){
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        String openid = json.getStr("openid");
        User user = userService.findByName(openid);
        if(user == null){
            user = new User();
            user.setUsername(openid);
            user.setNickName("微信用户");
            user.setGender("男");
            user.setPhone("1" + cn.hutool.core.date.DateUtil.format(new Date(), "yyyyMMddHHmmss"));
            user.setEmail(user.getPhone()+"@qq.com");
            user.setPassword(passwordEncoder.encode("123456"));
            user.setStatus("4");
            user.setEnabled(true);
            user.setDeptId(1L);
            user.setAvatarPath("132.jpg");
            Set<Job> jobs = new HashSet<>(1);
            Job job = new Job();
            job.setId(12L);
            jobs.add(job);
            user.setJobs( jobs);
            Set<Role> roles = new HashSet<>(1);
            Role role = new Role();
            role.setId(11L);
            roles.add(role);
            user.setRoles(roles);
            userService.createWei(user);
        }
        UsernamePasswordAuthenticationToken authenticationToken =  new UsernamePasswordAuthenticationToken(user.getUsername(), "123456");
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String token = tokenProvider.createToken(authentication);
        final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
        jwtUserDto.getUser().setPassword( null);
        // 返回 token 与 用户信息
        log.info("微信小程序登录openId 登录成功");
        authInfo.put("token", properties.getTokenStartWith() + token);
        authInfo.put("user", jwtUserDto);
        authInfo.put("code", "200");
        authInfo.put("openid", openid);
        authInfo.put("session_key", json.getStr("session_key"));
        if (loginProperties.isSingleLogin()) {
            // 踢掉之前已经登录的token
            onlineUserService.kickOutForUsername(authUser.getOpenid());
        }
        // 保存在线信息
        onlineUserService.save(jwtUserDto, token, request);
        // 返回登录信息
        return ResponseEntity.ok(authInfo);
    }

    @Log("新微信小程序获取手机号")
    @ApiOperation("新微信小程序获取手机号")
    @AnonymousPostMapping(value = "/weChatPhone")
    public ResponseEntity<Object> weChatPhone(@RequestBody WeiXinVo authUser) {
        String jsonStr = vxMsg.newVxPhoneUrl(authUser.getCode());
        Map<String, Object> authInfo = new HashMap<String, Object>(1);
        authInfo.put("phone", jsonStr);
        authInfo.put("code", "200");
        return ResponseEntity.ok(authInfo);
    }


    @ApiOperation("测试")
    @AnonymousPostMapping(value = "/ceshi")
    public ResponseEntity<Object> ceshi() {
        String jsonStr = vxMsg.getNewVxToken();
        Map<String, Object> authInfo = new HashMap<String, Object>(1);
        authInfo.put("accession", jsonStr);
        authInfo.put("code", "200");
        return ResponseEntity.ok(authInfo);
    }

}
